package il.ac.bgu.soa2009.hw5.restuarnt;

import il.ac.bgu.soa2009.hw5.schema.*;

import java.util.*;

public class Restaurant implements RestaurantIfc {
    private RestaurantType restaurantType = RestaurantType.NotSet;
    private int tablesNumber = -1;
    private OpenHour[] openningHours = new OpenHour[0];
    private Map<MyDate, Vector<TableReservation>> reservetionsByDate = new HashMap<MyDate, Vector<TableReservation>>();
    private Map<String, TableReservation> reservationsByName = new HashMap<String, TableReservation>();


    public void setRestaurantType(RestaurantType type) {
        this.restaurantType = type;
    }

    public void setTablesNumber(int tablesNumber) throws NegativeTablesNumberException {
        if (tablesNumber < 0) {
            throw new NegativeTablesNumberException("Tables number must be positive! Got:  " + tablesNumber);
        } else {
            this.tablesNumber = tablesNumber;
        }
    }

    public void setOpeningHours(OpenHour[] openningHours) throws RestaurantException {
        Set<WeekDay> set = new HashSet<WeekDay>();
        for (OpenHour openHour : openningHours) {
            WeekDay weekDay = openHour.getWeekDay();
            if (set.contains(weekDay)) {
                throw new BadOpenningHoursConfiguration(weekDay.getValue() + " appears twice!");
            } else {
                if (Integer.parseInt(openHour.getFrom().getValue()) >= Integer.parseInt(openHour.getTo().getValue())) {
                    throw new BadOpenningHoursConfiguration("Open hour must be prior to close hour!");
                }
                set.add(weekDay);
            }
        }
        this.openningHours = openningHours;
    }

    public RestaurantDetails getRestaurantDetails() throws RestaurantException {
        RestaurantDetails details = new RestaurantDetails();
        details.setType(restaurantType);
        details.setOpeningHours(openningHours);
        return details;
    }

    public void reserveTable(TableReservation reservation) throws RestaurantException {
        if (restaurantType == RestaurantType.NotSet) {
            throw new RestaurantException("Restaurant type is not yet set!");
        }
        if (tablesNumber == -1) {
            throw new NumberOfTablesIsNotSetException("Tables number must be set!");
        }
        TableReservation previousReservation = reservationsByName.get(reservation.getCostumerName().getName());
        if (previousReservation != null) {
            MyDate myDate = new MyDate(previousReservation.getDate());
            throw new BadReservationException("You allready have a reservation on the " + myDate);
        }
        Date date = reservation.getDate();
        date.setHours(Integer.parseInt(reservation.getTime().getValue()));
        date.setMinutes(0);
        date.setSeconds(0);
        Date now = new Date();
        if (now.getTime() > date.getTime()) {
            throw new BadReservationException("Reservation must be in the future");
        }

        MyDate myDate = new MyDate(date);
        WeekDay day = DateUtils.getDayOfWeek(date);
        OpenHour openHour = getOpenningHoursForDay(day);
        if (openHour == null) {//the resturant is colsed on that day
            throw new BadReservationException("The restaurant is closed on " + day);
        } else {
            int reservationHour = Integer.parseInt(reservation.getTime().getValue());
            int restaruntOpenHour = Integer.parseInt(openHour.getFrom().getValue());
            if (reservationHour < restaruntOpenHour) {
                throw new BadReservationException("The restaurant is open starting from " + restaruntOpenHour + " on " + day);
            }
            int restaruntCloseHour = Integer.parseInt(openHour.getTo().getValue());
            if (reservationHour > restaruntCloseHour) {
                throw new BadReservationException("The restaurant is closed starting from " + restaruntCloseHour + " on " + day);
            }
            Vector<TableReservation> reservationsOnThatDay = reservetionsByDate.get(myDate);
            if (reservationsOnThatDay == null) {
                reservationsOnThatDay = new Vector<TableReservation>();
                reservationsOnThatDay.add(reservation);
                reservetionsByDate.put(myDate, reservationsOnThatDay);
                reservationsByName.put(reservation.getCostumerName().getName(), reservation);
            } else {
                int interceptions = 0;
                for (TableReservation tableReservation : reservationsOnThatDay) {
                    if (areIntercepted(tableReservation, reservation)) {
                        interceptions++;
                    }
                }
                if (interceptions < tablesNumber) {
                    reservationsOnThatDay.add(reservation);
                    reservationsByName.put(reservation.getCostumerName().getName(), reservation);
                } else {
                    throw new BadReservationException("Sorry, but there are no tables free for your reservation's parameters.");
                }
            }
        }
    }

    private boolean areIntercepted(TableReservation reservationA, TableReservation reservationB) {
        MyDate myDateA = new MyDate(reservationA.getDate());
        MyDate myDateB = new MyDate(reservationB.getDate());
        return myDateA.equals(myDateB) && reservationA.getTime().getValue().equals(reservationB.getTime().getValue());
    }

    private OpenHour getOpenningHoursForDay(WeekDay day) {
        for (OpenHour openHour : openningHours) {
            if (openHour.getWeekDay().equals(day)) {
                return openHour;
            }
        }
        return null;
    }

    public void cancelReservation(ReservationName parameter) throws RestaurantException {
        TableReservation tableReservation = reservationsByName.get(parameter.getName());
        if (tableReservation == null) {
            throw new BadCancelReservationRequest("There is no reservetion under the name " + parameter.getName());
        } else {
            reservationsByName.remove(parameter.getName());
            Vector<TableReservation> tableReservations = reservetionsByDate.get(new MyDate(tableReservation.getDate()));
            for (TableReservation reservation : tableReservations) {
                if (reservation.getCostumerName().getName().equals(parameter.getName())) {
                    tableReservations.remove(reservation);
                    break;
                }
            }
        }
    }
}
